@phenix1024/editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +217 -122
- package/dist/ophx-editor.cjs +59 -1
- package/dist/ophx-editor.css +1 -1
- package/dist/ophx-editor.iife.js +102 -44
- package/dist/ophx-editor.iife.js.map +1 -1
- package/dist/ophx-editor.js +1108 -561
- package/dist/ophx-editor.standalone.js +4132 -3585
- package/dist/ophx-editor.standalone.js.map +1 -1
- package/dist/types/editor/extensions.d.ts +2 -0
- package/dist/types/editor/ophx-editor.d.ts +45 -2
- package/dist/types/editor/toolbar.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,189 +1,284 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @phenix1024/editor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Редактор документов для браузера на базе [Tiptap](https://tiptap.dev). Работает без фреймворков (подойдут React, Vue, Svelte, Angular или чистый JS), ему нужен только DOM-элемент. Документы сохраняются в переносимый архив `.ophx` (HTML + CSS + изображения в одном ZIP).
|
|
4
|
+
|
|
5
|
+
- Заголовки, списки, отступы, таблицы, изображения с обтеканием, ссылки и цветные метки.
|
|
6
|
+
- Markdown-подобный ввод: `# ` даёт заголовок, `- ` маркированный список, `1. ` нумерованный.
|
|
7
|
+
- Готовая панель инструментов: стиль текста, поиск с подсветкой, содержание, отмена и повтор.
|
|
8
|
+
- Режим просмотра, который запоминается между перезагрузками страницы.
|
|
9
|
+
- Поля шаблонов: заполняемые места в документе, сбор значений, проверка обязательных полей.
|
|
4
10
|
|
|
5
|
-
|
|
11
|
+
## Установка
|
|
6
12
|
|
|
7
13
|
```bash
|
|
8
14
|
npm i @phenix1024/editor
|
|
9
15
|
```
|
|
10
16
|
|
|
17
|
+
## Быстрый старт
|
|
18
|
+
|
|
19
|
+
```html
|
|
20
|
+
<div id="toolbar"></div>
|
|
21
|
+
<div id="editor"></div>
|
|
22
|
+
```
|
|
23
|
+
|
|
11
24
|
```ts
|
|
12
|
-
import { OphxEditor
|
|
25
|
+
import { OphxEditor } from '@phenix1024/editor'
|
|
13
26
|
import '@phenix1024/editor/style.css'
|
|
14
27
|
|
|
15
28
|
const editor = new OphxEditor({
|
|
16
|
-
element:
|
|
17
|
-
|
|
29
|
+
element: '#editor', // элемент или CSS-селектор
|
|
30
|
+
toolbar: '#toolbar', // необязательно: встроенная панель инструментов
|
|
31
|
+
content: '<h1>Документ</h1><p>Начните писать…</p>',
|
|
32
|
+
onUpdate: () => localStorage.setItem('draft', editor.getHTML()),
|
|
18
33
|
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Готово: редактор с панелью работает. Когда редактор больше не нужен, вызовите `editor.destroy()`. Он уберёт и панель.
|
|
37
|
+
|
|
38
|
+
## Частые задачи
|
|
39
|
+
|
|
40
|
+
### Получить и задать содержимое
|
|
19
41
|
|
|
20
|
-
|
|
42
|
+
```ts
|
|
43
|
+
const html = editor.getHTML() // для автосохранения и отправки на сервер
|
|
44
|
+
const json = editor.getJSON() // структура документа Tiptap
|
|
45
|
+
const text = editor.getText() // простой текст
|
|
46
|
+
|
|
47
|
+
editor.setContent('<p>Новый текст</p>') // onUpdate не вызывается
|
|
48
|
+
editor.setContent('<p>Новый текст</p>', { emitUpdate: true })
|
|
49
|
+
editor.clearContent()
|
|
21
50
|
```
|
|
22
51
|
|
|
23
|
-
###
|
|
52
|
+
### Режим просмотра
|
|
24
53
|
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
54
|
+
```ts
|
|
55
|
+
editor.setEditable(false) // только чтение
|
|
56
|
+
editor.setEditable(true) // снова редактирование
|
|
57
|
+
editor.isEditable // текущий режим
|
|
58
|
+
```
|
|
30
59
|
|
|
31
|
-
|
|
32
|
-
let unmountToolbar: (() => void) | null = null
|
|
60
|
+
Режим сохраняется в `localStorage` и восстанавливается после перезагрузки. В режиме просмотра панель отключает инструменты форматирования, а поиск и содержание остаются доступными.
|
|
33
61
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
62
|
+
| Опция | Поведение |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `editable: false` | Начальный режим, если сохранённого значения ещё нет. |
|
|
65
|
+
| `editableStorageKey: 'my-key'` | Свой ключ `localStorage`, например отдельный для каждого документа. |
|
|
66
|
+
| `editableStorageKey: null` | Не запоминать режим: всегда используется `editable`. |
|
|
67
|
+
|
|
68
|
+
### Сохранить и открыть `.ophx`
|
|
38
69
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
```ts
|
|
71
|
+
// Скачать файл в браузере
|
|
72
|
+
await editor.downloadOphx({ filename: 'Договор', title: 'Договор поставки' })
|
|
73
|
+
|
|
74
|
+
// Или получить Blob, например чтобы отправить на сервер
|
|
75
|
+
const blob = await editor.saveOphx({ filename: 'Договор' })
|
|
76
|
+
|
|
77
|
+
// Открыть файл, выбранный пользователем
|
|
78
|
+
input.addEventListener('change', async () => {
|
|
79
|
+
const file = input.files?.[0]
|
|
80
|
+
if (!file) return
|
|
81
|
+
const doc = await editor.loadOphx(file) // { title, filename, styles, css, html, ... }
|
|
82
|
+
console.log(doc.title)
|
|
42
83
|
})
|
|
43
|
-
</script>
|
|
44
84
|
```
|
|
45
85
|
|
|
46
|
-
|
|
86
|
+
После `loadOphx()` имя, заголовок и стили архива запоминаются. Следующий `saveOphx()` или `downloadOphx()` без параметров сохранит документ с теми же метаданными. Параметры, переданные явно, имеют приоритет.
|
|
47
87
|
|
|
48
|
-
|
|
49
|
-
<link rel="stylesheet" href="https://unpkg.com/@phenix1024/editor@0.1.0/dist/ophx-editor.css">
|
|
50
|
-
<script src="https://unpkg.com/@phenix1024/editor@0.1.0/dist/ophx-editor.iife.js"></script>
|
|
51
|
-
<script>
|
|
52
|
-
const editor = new OphxEditor.OphxEditor({ element: document.querySelector('#editor') })
|
|
53
|
-
OphxEditor.mountToolbar(document.querySelector('#toolbar'), editor.editor)
|
|
54
|
-
</script>
|
|
55
|
-
```
|
|
88
|
+
### Свои действия поверх Tiptap
|
|
56
89
|
|
|
57
|
-
|
|
90
|
+
`editor.editor` — это исходный экземпляр Tiptap `Editor`. Через него доступны все команды и события:
|
|
58
91
|
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
|
|
92
|
+
```ts
|
|
93
|
+
editor.editor.chain().focus().toggleBold().run()
|
|
94
|
+
editor.editor.on('selectionUpdate', () => { /* ... */ })
|
|
62
95
|
```
|
|
63
96
|
|
|
64
|
-
|
|
97
|
+
Дополнительные расширения Tiptap передаются через `extensions: [...]`.
|
|
65
98
|
|
|
66
|
-
|
|
99
|
+
### Загрузка изображений на сервер
|
|
67
100
|
|
|
68
|
-
|
|
101
|
+
По умолчанию панель вставляет изображения как `data:` URL, а при сохранении они упаковываются в `.ophx`. Чтобы загружать их на свой сервер:
|
|
69
102
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
103
|
+
```ts
|
|
104
|
+
const editor = new OphxEditor({
|
|
105
|
+
element: '#editor',
|
|
106
|
+
toolbar: '#toolbar',
|
|
107
|
+
toolbarOptions: {
|
|
108
|
+
onInsertImage: async (file) => {
|
|
109
|
+
const url = await uploadToServer(file)
|
|
110
|
+
editor.editor.chain().focus().setImage({ src: url, alt: file.name }).run()
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
```
|
|
78
115
|
|
|
79
|
-
|
|
116
|
+
### Метки
|
|
80
117
|
|
|
81
|
-
|
|
118
|
+
Выделите текст и нажмите «Метка» на панели. Во всплывающем окне введите название или выберите существующее. Клик по метке показывает её название, а в режиме редактирования там же её можно изменить или удалить. Метки сохраняются как `<mark data-label="...">`. Метки «важно» и «идея» имеют свои цвета, цвета остальных задаются в CSS:
|
|
82
119
|
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
npm run dev
|
|
86
|
-
npm run typecheck
|
|
87
|
-
npm run build
|
|
120
|
+
```css
|
|
121
|
+
.ophx-editor-surface mark[data-label="срочно"] { background: #ffe0b3; }
|
|
88
122
|
```
|
|
89
123
|
|
|
90
|
-
|
|
124
|
+
### Поля шаблонов
|
|
91
125
|
|
|
92
|
-
|
|
126
|
+
Поле — это элемент с классом `ophx-field`, атрибутом `contenteditable="true"` и уникальным `data-ophx-field`:
|
|
93
127
|
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
|
|
128
|
+
```html
|
|
129
|
+
<p>Покупатель: <span class="ophx-field" contenteditable="true"
|
|
130
|
+
data-ophx-field="buyer" data-ophx-label="Покупатель" data-ophx-required="true"></span></p>
|
|
131
|
+
```
|
|
97
132
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
onUpdate: ({ editor }) => autosave(editor.getHTML()),
|
|
102
|
-
})
|
|
103
|
-
const unmountToolbar = mountToolbar(document.querySelector('#toolbar')!, editor.editor)
|
|
133
|
+
```ts
|
|
134
|
+
editor.getFields() // [{ id, label, required, value }]
|
|
135
|
+
editor.getMissingRequiredFields() // незаполненные обязательные поля
|
|
104
136
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
editor.destroy()
|
|
137
|
+
import { convertUnderscoresToFields } from '@phenix1024/editor'
|
|
138
|
+
const { html } = convertUnderscoresToFields('<p>ФИО: ______</p>') // «___» превращается в поля f1, f2, …
|
|
108
139
|
```
|
|
109
140
|
|
|
110
|
-
|
|
141
|
+
Фрагменты с `contenteditable="false"` сохраняются и остаются нередактируемыми.
|
|
142
|
+
|
|
143
|
+
## Фреймворки
|
|
144
|
+
|
|
145
|
+
### React
|
|
111
146
|
|
|
112
147
|
```tsx
|
|
113
148
|
import { useEffect, useRef } from 'react'
|
|
114
|
-
import { OphxEditor
|
|
115
|
-
import '@
|
|
149
|
+
import { OphxEditor } from '@phenix1024/editor'
|
|
150
|
+
import '@phenix1024/editor/style.css'
|
|
116
151
|
|
|
117
|
-
export function DocumentEditor() {
|
|
118
|
-
const
|
|
119
|
-
const
|
|
152
|
+
export function DocumentEditor({ initialHtml }: { initialHtml?: string }) {
|
|
153
|
+
const editorRef = useRef<HTMLDivElement>(null)
|
|
154
|
+
const toolbarRef = useRef<HTMLDivElement>(null)
|
|
120
155
|
|
|
121
156
|
useEffect(() => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
157
|
+
const editor = new OphxEditor({
|
|
158
|
+
element: editorRef.current!,
|
|
159
|
+
toolbar: toolbarRef.current!,
|
|
160
|
+
content: initialHtml,
|
|
161
|
+
})
|
|
162
|
+
return () => editor.destroy()
|
|
126
163
|
}, [])
|
|
127
164
|
|
|
128
|
-
return
|
|
165
|
+
return (
|
|
166
|
+
<>
|
|
167
|
+
<div ref={toolbarRef} />
|
|
168
|
+
<div ref={editorRef} />
|
|
169
|
+
</>
|
|
170
|
+
)
|
|
129
171
|
}
|
|
130
172
|
```
|
|
131
173
|
|
|
132
|
-
|
|
174
|
+
### Vue 3
|
|
133
175
|
|
|
134
|
-
|
|
176
|
+
```vue
|
|
177
|
+
<script setup lang="ts">
|
|
178
|
+
import { onMounted, onBeforeUnmount, ref } from 'vue'
|
|
179
|
+
import { OphxEditor } from '@phenix1024/editor'
|
|
180
|
+
import '@phenix1024/editor/style.css'
|
|
135
181
|
|
|
136
|
-
|
|
182
|
+
const editorEl = ref<HTMLElement>()
|
|
183
|
+
const toolbarEl = ref<HTMLElement>()
|
|
184
|
+
let editor: OphxEditor | undefined
|
|
137
185
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
186
|
+
onMounted(() => {
|
|
187
|
+
editor = new OphxEditor({ element: editorEl.value!, toolbar: toolbarEl.value! })
|
|
188
|
+
})
|
|
189
|
+
onBeforeUnmount(() => editor?.destroy())
|
|
190
|
+
</script>
|
|
191
|
+
|
|
192
|
+
<template>
|
|
193
|
+
<div ref="toolbarEl" />
|
|
194
|
+
<div ref="editorEl" />
|
|
195
|
+
</template>
|
|
143
196
|
```
|
|
144
197
|
|
|
145
|
-
|
|
146
|
-
import { exportOphx, importOphx, downloadBlob } from '@phenix1024/editor'
|
|
198
|
+
Не храните экземпляр `OphxEditor` в `ref()` или `reactive()`: прокси Vue мешают работе Tiptap.
|
|
147
199
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
editor.
|
|
158
|
-
|
|
159
|
-
filename: document.filename,
|
|
160
|
-
title: document.title,
|
|
161
|
-
styles: document.styles,
|
|
162
|
-
bodyContentEditable: document.bodyContentEditable,
|
|
163
|
-
})
|
|
164
|
-
downloadBlob(updatedArchive, `${document.filename}.ophx`)
|
|
165
|
-
// document.styles содержит все подключённые локальные CSS-файлы.
|
|
166
|
-
// document.css — удобный алиас первого stylesheet.
|
|
200
|
+
### Без сборщика (CDN)
|
|
201
|
+
|
|
202
|
+
```html
|
|
203
|
+
<link rel="stylesheet" href="https://unpkg.com/@phenix1024/editor@0.2.0/dist/ophx-editor.css">
|
|
204
|
+
<script src="https://unpkg.com/@phenix1024/editor@0.2.0/dist/ophx-editor.iife.js"></script>
|
|
205
|
+
|
|
206
|
+
<div id="toolbar"></div>
|
|
207
|
+
<div id="editor"></div>
|
|
208
|
+
<script>
|
|
209
|
+
const editor = new OphxEditor.OphxEditor({ element: '#editor', toolbar: '#toolbar' })
|
|
210
|
+
</script>
|
|
167
211
|
```
|
|
168
212
|
|
|
169
|
-
|
|
213
|
+
Через jsDelivr: `https://cdn.jsdelivr.net/npm/@phenix1024/editor@0.2.0/dist/...`. Версию в ссылке лучше фиксировать.
|
|
170
214
|
|
|
171
|
-
|
|
215
|
+
## API
|
|
172
216
|
|
|
173
|
-
###
|
|
217
|
+
### `new OphxEditor(options)`
|
|
218
|
+
|
|
219
|
+
| Опция | Тип | По умолчанию | Описание |
|
|
220
|
+
| --- | --- | --- | --- |
|
|
221
|
+
| `element` | `HTMLElement \| string` | — | Контейнер документа (обязательно). |
|
|
222
|
+
| `toolbar` | `HTMLElement \| string` | — | Контейнер встроенной панели инструментов. |
|
|
223
|
+
| `toolbarOptions` | `{ onInsertImage?(file) }` | — | Настройки панели. |
|
|
224
|
+
| `content` | `string \| JSON` | `'<p></p>'` | Начальное содержимое. |
|
|
225
|
+
| `placeholder` | `string` | `'Начните писать…'` | Подсказка в пустом документе. |
|
|
226
|
+
| `editable` | `boolean` | `true` | Начальный режим, если нет сохранённого. |
|
|
227
|
+
| `editableStorageKey` | `string \| null` | `'ophx-editor-editable'` | Ключ `localStorage` для режима; `null` отключает хранение. |
|
|
228
|
+
| `onUpdate` | `({ editor }) => void` | — | Вызывается при каждом изменении документа. |
|
|
229
|
+
| `extensions` | `Extension[]` | `[]` | Дополнительные расширения Tiptap. |
|
|
230
|
+
|
|
231
|
+
### Методы и свойства
|
|
232
|
+
|
|
233
|
+
| | |
|
|
234
|
+
| --- | --- |
|
|
235
|
+
| `getHTML()`, `getJSON()`, `getText()` | Содержимое документа. |
|
|
236
|
+
| `setContent(content, { emitUpdate? })`, `clearContent()` | Замена содержимого. |
|
|
237
|
+
| `setEditable(bool)`, `isEditable` | Режим редактирования или просмотра. |
|
|
238
|
+
| `isEmpty` | Пустой ли документ. |
|
|
239
|
+
| `focus(pos?)`, `blur()` | Фокус: `'start'`, `'end'`, `'all'` или позиция. |
|
|
240
|
+
| `undo()`, `redo()`, `canUndo()`, `canRedo()` | История изменений. |
|
|
241
|
+
| `setSearchQuery(query)` | Подсветка найденного текста. |
|
|
242
|
+
| `loadOphx(file)` | Открыть `.ophx`, возвращает `OphxDocument`. |
|
|
243
|
+
| `saveOphx(options?)` | Создать `.ophx` и вернуть `Blob`. |
|
|
244
|
+
| `downloadOphx(options?)` | Скачать `.ophx`. |
|
|
245
|
+
| `getFields()`, `getMissingRequiredFields()` | Поля шаблона. |
|
|
246
|
+
| `destroy()` | Удалить редактор и панель. |
|
|
247
|
+
| `editor` | Исходный Tiptap `Editor`. |
|
|
248
|
+
|
|
249
|
+
Параметры сохранения (`OphxExportOptions`): `filename` (имя без `.ophx`), `title`, `styles` (`{ 'имя.css': 'css…' }`), `assets` (файлы изображений по имени), `bodyContentEditable`.
|
|
250
|
+
|
|
251
|
+
### Другие экспорты
|
|
252
|
+
|
|
253
|
+
- `mountToolbar(host, tiptapEditor, options?)` — смонтировать панель вручную. Возвращает функцию очистки.
|
|
254
|
+
- `exportOphx(html, options)`, `importOphx(file)`, `downloadBlob(blob, filename)` — работа с архивами без редактора.
|
|
255
|
+
- `convertUnderscoresToFields(html, prefix?)` — превращает `___` в поля шаблона.
|
|
256
|
+
- Расширения Tiptap: `Label`, `CollapsibleHeading`, `Indentation`, `OphxInlineField`, `OphxBlockField`, `OphxReadonlyBlock`, `OphxReadonlyInline`, `OphxEditability`, `TextSearch`, `WysiwymRules`.
|
|
174
257
|
|
|
175
|
-
|
|
258
|
+
## Формат `.ophx`
|
|
176
259
|
|
|
177
|
-
|
|
260
|
+
Обычный ZIP, все файлы лежат в корне. Внутри ровно один HTML, его имя совпадает с именем архива. Рядом лежат CSS и изображения:
|
|
178
261
|
|
|
179
|
-
|
|
262
|
+
```text
|
|
263
|
+
Договор.ophx
|
|
264
|
+
├── Договор.html
|
|
265
|
+
├── default.css
|
|
266
|
+
└── image-1.png
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
- Вставленные изображения (`data:`, `blob:`) упаковываются как `image-<n>.<ext>`. Внешние `http(s)` ссылки остаются ссылками.
|
|
270
|
+
- При открытии из HTML удаляются `<script>` и обработчики событий (`on*`).
|
|
271
|
+
- CSS из архива возвращается в `doc.styles` и к странице автоматически не применяется. Где его применять, решает приложение.
|
|
272
|
+
|
|
273
|
+
## Разработка
|
|
274
|
+
|
|
275
|
+
```sh
|
|
276
|
+
npm install
|
|
277
|
+
npm run dev # демо на http://localhost:5173
|
|
278
|
+
npm run typecheck
|
|
279
|
+
npm run build # демо в dist-demo/, библиотека в dist/
|
|
280
|
+
```
|
|
180
281
|
|
|
181
|
-
|
|
182
|
-
- `editor.editor` — исходный Tiptap `Editor` для команд и подписок.
|
|
183
|
-
- `getHTML()`, `getJSON()`, `setContent(content)`, `setSearchQuery(query)`, `destroy()`.
|
|
184
|
-
- `mountToolbar(host, editor, { onInsertImage? })` возвращает функцию очистки.
|
|
185
|
-
- `exportOphx(html, { filename, title?, styles?, assets?, bodyContentEditable? })`, `importOphx(file)`, `downloadBlob(blob, filename)`.
|
|
186
|
-
- `getFields()` и `getMissingRequiredFields()` возвращают валидные уникальные поля шаблона.
|
|
187
|
-
- `convertUnderscoresToFields(html, prefix?)` возвращает преобразованный HTML, число полей и их IDs.
|
|
282
|
+
## Лицензия
|
|
188
283
|
|
|
189
|
-
|
|
284
|
+
MIT
|